Skip to content

feat: replace python-dateutil with stdlib datetime.fromisoformat#1429

Merged
dbanty merged 4 commits into
openapi-generators:mainfrom
splch:drop-python-dateutil
May 30, 2026
Merged

feat: replace python-dateutil with stdlib datetime.fromisoformat#1429
dbanty merged 4 commits into
openapi-generators:mainfrom
splch:drop-python-dateutil

Conversation

@splch
Copy link
Copy Markdown
Contributor

@splch splch commented Apr 24, 2026

Summary

Drop the python-dateutil dependency from both the generator and all generated client packages. Date/datetime parsing now uses Python's built-in datetime.fromisoformat() instead of dateutil.parser.isoparse().

This removes one runtime dependency from every generated client, reducing install size and eliminating a dependency that is unmaintained upstream and being deprecated by Fedora 45 (no releases since March 2024).

Changes

Generator source (openapi_python_client/parser/properties/):

  • datetime.py: Validate defaults with datetime.datetime.fromisoformat() instead of isoparse(). Normalize Z to +00:00 at generation time so the emitted Python code is clean.
  • date.py: Validate defaults with datetime.date.fromisoformat() instead of isoparse().date().
  • Both: Remove "from dateutil.parser import isoparse" from the import set returned by get_imports().

Jinja templates (openapi_python_client/templates/property_templates/):

  • datetime_property.py.jinja: isoparse(x) -> datetime.datetime.fromisoformat(x.replace("Z", "+00:00"))
  • date_property.py.jinja: isoparse(x).date() -> datetime.date.fromisoformat(x)

Dependency removal (all 4 metadata formats):

  • pyproject_uv.toml.jinja, pyproject_poetry.toml.jinja, pyproject_pdm.toml.jinja, setup.py.jinja: Remove python-dateutil from generated dependencies.
  • pyproject.toml (generator): Remove python-dateutil from runtime deps and types-python-dateutil from dev deps.
  • integration-tests/pyproject.toml: Remove both as well.

Tests & golden records: All regenerated. Unit tests pass (283 passed, 4 skipped).

Python 3.10 compatibility

datetime.fromisoformat() gained full ISO 8601 support (including the Z suffix) in Python 3.11. On Python 3.10, the Z suffix raises a ValueError:

# Python 3.10
>>> datetime.datetime.fromisoformat("2024-01-15T10:30:00Z")
ValueError: Invalid isoformat string: '2024-01-15T10:30:00Z'

>>> datetime.datetime.fromisoformat("2024-01-15T10:30:00+00:00")
datetime.datetime(2024, 1, 15, 10, 30, tzinfo=datetime.timezone.utc)  # works
# Python 3.11+
>>> datetime.datetime.fromisoformat("2024-01-15T10:30:00Z")
datetime.datetime(2024, 1, 15, 10, 30, tzinfo=datetime.timezone.utc)  # works natively

The generated datetime parsing code uses .replace("Z", "+00:00") to normalize Z to an explicit UTC offset before calling fromisoformat(), which works on both 3.10 and 3.11+. This is a no-op on strings without Z. Date parsing does not need this since date strings have no timezone component.

Default values in OpenAPI specs are normalized at generation time (Z -> +00:00), so the emitted default expressions are clean datetime.datetime.fromisoformat("...") calls.

Drop the python-dateutil dependency from both the generator and all
generated client code. Date/datetime parsing now uses the stdlib:

- datetime fields: datetime.datetime.fromisoformat(v.replace("Z", "+00:00"))
- date fields: datetime.date.fromisoformat(v)

The .replace("Z", "+00:00") call is needed because Python 3.10's
fromisoformat() does not accept the Z timezone suffix (added in 3.11).
It is a no-op on strings that do not contain Z.

Default values in OpenAPI specs are normalized at generation time
(Z replaced with +00:00), so the generated default expressions are
clean datetime.datetime.fromisoformat("...") calls without the
replace.

This removes one runtime dependency from every generated client
package, reducing install size and eliminating a dependency that is
in maintenance-only mode upstream.
Comment thread openapi_python_client/parser/properties/datetime.py
Comment thread pdm.minimal.lock Outdated
splch added 3 commits May 27, 2026 14:17
Make the .replace("Z", "+00:00") workaround discoverable by both
`rg TODO` and `rg "3.10|py3.10"` so it can be cleanly removed
when the project drops Python 3.10 support.
Brings in 8 commits since the PR opened, notably:
  - typer constraint bump to <0.27
  - uv_build 0.11 template update
  - mypy v2 cast cleanup in generated output

Conflict resolution:
  - pdm.lock, integration-tests/pdm.lock: regenerated via 'pdm lock'
    against the merged pyproject.toml.
  - Golden records: regenerated via 'pdm run regen' to absorb the
    mypy-cast cleanup; the dateutil -> fromisoformat change from this
    branch is preserved.

Verified locally with ruff check, ruff format --check, mypy, and the
unit-test suite (283 passed, 4 skipped).
The minimal lockfiles were previously regenerated without
'-S direct_minimal_versions', so direct dependencies were locked
to highest-compatible versions instead of lowest. That defeats the
point of the test_min_deps CI job, which exists to verify the
declared lower bounds in pyproject.toml still work.

Re-locked both pdm.minimal.lock files using:
    pdm lock -S direct_minimal_versions -L pdm.minimal.lock

Strategy marker confirmed as
["direct_minimal_versions", "inherit_metadata"] in the metadata
of both files. python-dateutil is absent (this branch's main change).
Copy link
Copy Markdown
Collaborator

@dbanty dbanty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@dbanty dbanty added this pull request to the merge queue May 30, 2026
Merged via the queue into openapi-generators:main with commit 6df45e9 May 30, 2026
22 checks passed
@knope-bot knope-bot Bot mentioned this pull request May 30, 2026
@dbanty
Copy link
Copy Markdown
Collaborator

dbanty commented May 30, 2026

Turns out fractional seconds also aren't really supported in Python 3.10, caught by intermittent failures in the integration tests 😒. I think I'll just drop 3.10 support for this release, there's only a few months left of security support for it anyway.

iloveitaly pushed a commit to iloveitaly/openapi-python-client that referenced this pull request May 31, 2026
> [!IMPORTANT]
> Merging this pull request will create this release

## Breaking Changes

- Drop support for Python 3.10
- Raise minimum httpx version to 0.23.1

## Features

- replace python-dateutil with stdlib datetime.fromisoformat (openapi-generators#1429)

## Fixes

- Remove some generated casts that aren't necessary with mypy v2 (openapi-generators#1436)
- Explicitly set boundary for multipart/form-data (openapi-generators#1005)

Co-authored-by: knope-bot[bot] <152252888+knope-bot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants